<?php
require_once 'IDump.php';

class Dump implements IDump {
	private $htmlOff;
	private $result;

	/**
	 * @param mixed $object
	 * @param bool $plain
	 */
	public function __construct ($object, $plain = false) {
		$this->htmlOff = $plain;
		$this->result  = $this->objectDump($object);
		if (!$plain) {
			$this->result = '<pre>' . $this->result . '</pre>';
		}
	}

	/**
	 * @return string
	 */
	public function __toString () {
		return $this->result;
	}
	
	/**
	 * @return string
	 */
	public function get () {
		return $this->result;
	}

	private function objectDump ($object, $key = null, $parents = array()) {
		$result = !is_null($key) ? $this->html($key, 'KEY') . ' : ' : '';
		$recurs = (in_array($object, $parents, true)) ?
			$this->html('*RECURSION*', 'RECURS') : null;

		$level = count ($parents);
		$parents[] = $object;
		if (is_int ($object) OR is_float ($object) OR $object instanceof Number) {
			if ($object instanceof Number) {
				$result .= $this->html('(Number) ', 'TYPE');
			}
			$result .= $this->html($object, 'NUM');
		} else if (is_string($object) or $object instanceof String) {
			if ($object instanceof String) {
				$result .= $this->html('(String) ', 'TYPE');
			}
			$result .= "'" . $this->html($object, 'STR') . "'";
		} else {
			if (is_array($object)) {
				$result .= $this->arrDump($object, $recurs, $parents);
			} else if ($object instanceof Map) {
				$result .= $this->mapDump($object, $recurs, $parents);
			} else if (is_object($object)) {
				$result .= $this->objDump($object, $recurs, $parents);
			} else if (is_bool($object)) {
				$result .= $this->html($object ? 'true' : 'false', 'NULL');
			} else {
				$result .= $this->html('null', 'NULL');
			}
		}
		return $result;
	}

	private function mapDump ($object, $recurs, $parents) {
		$count = count($parents);
		$rc = new ReflectionClass($object);
		$result = $this->html($rc->getName(), 'CLASS') . " [";
		if ($recurs) {
			$result .= $recurs;
		} else {
			$isEmpty = true;
			foreach ($object->getAtom() as $k => $v) {
				$result .= $this->addSpaces($count);
				$result .= $this->objectDump($v, $k, $parents);
				$isEmpty = false;
			}
			if(!$isEmpty) {
				$result .= $this->addspaces($count-1);
			}
		}
		$result .= ']';
		return $result;
	}

	private function objDump ($object, $recurs, $parents) {
		$count = count($parents);
		$rc = new ReflectionClass($object);
		$result = $this->html($rc->getName(), 'CLASS') . " [";
		if ($recurs) {
			$result .= $recurs;
		} else {
			$props = $this->dumpProperties($object);
			foreach ($props as $p) {
				$type = ($p['type'] == 'public') ? '' : $this->html(" ($p[type])", 'TYPE');
				$result .= $this->addSpaces($count) . $this->html($p['name'], 'KEY') . $type;
				$result .= ' : ' . $this->objectDump($p['value'], null, $parents);
			}
		}
		if (!$recurs) {
			$result .= $this->addspaces($count-1);
		}
		$result .= ']';
		return $result;
	}

	private function arrDump ($object, $recurs, $parents) {
		$count   = count($parents);
		$result  = '[';
		$isEmpty = true;
		if ($recurs) {
			$result .= $this->addSpaces($count);
			$result .= $recurs;
			$isEmpty = false;
		} else {
			foreach ($object as $k => $v) {
				$result .= $this->addSpaces($count);
				$result .= $this->objectDump($v, $k, $parents);
				$isEmpty = false;
			}
		}
		if(!$isEmpty) {
			$result .= $this->addspaces($count-1);
		}
		$result .= ']';
		return $result;
	}

	private function addSpaces ($count) {
		return "\n" . str_repeat("\t", $count);
	}

	private function dumpProperties ($obj) {
		$props = array();

		foreach ((array)$obj as $name => $value) {
			$type = 'public';

			if ($name[0] == "\0") {
				$type = ($name[1] == '*') ?
					'protected' : 'private';
				$name = substr($name, strpos($name, "\0", 1) + 1);
			}

			$props[] = array(
				'name'  => $name,
				'value' => $value,
				'type'  => $type
			);
		}

		return $props;
	}

	private function html ($str, $type) {
		if ($this->htmlOff) {
			return $str;
		} else {
			$styles = array (
				'DEFAULT' => 'color:#000;',
				'RECURS'  => 'color:#c00;',
				'CLASS'   => 'color:#33f',
				'NULL'    => 'color:#c00',
				'KEY'     => 'color:#90c',
				'ELEM'    => 'color:#060',
				'TYPE'    => 'color:#333; font-size:0.7em; vertical-align:middle;',
				'STR'     => 'color:#060',
				'NUM'     => 'color:#399',
			);
			$style = isset($styles[$type]) ? $styles[$type] : $styles['DEFAULT'];
			return "<span style='$style'>$str</span>";
		}
	}
}